package edu.gmu.atelier;

import java.util.ArrayList;
import java.util.List;

/**
 * Vector defined within some vector space.
 * @author James H. Pope
 */
public class ElementVector implements Vector
{
    private Element[] elements = null;
    
    /**
     * Creates a new vector in V^3 where V defined by the Element.
     * @param comps 
     */
    public ElementVector(Element e1, Element e2)
    {
        this( new Element[]{e1,e2} );
    }
    
    /**
     * Creates a new vector in V^3 where V defined by the Element.
     * @param comps 
     */
    public ElementVector(Element e1, Element e2, Element e3)
    {
        this( new Element[]{e1,e2,e3} );
    }
    
    /**
     * Creates a new vector in V^(comps.len) where V defined by the Element.
     * @param comps 
     */
    public ElementVector(Element[] comps)
    {
        this.elements = comps;
    }
    
    //------------------------------------------------------------------------//
    // Interface methods
    //------------------------------------------------------------------------//
    public Vector normalize()
    {
        Element normInv = norm2().sqrt().inverse();
        
        Element[] elems = new Element[this.elements.length];
        for (int i = 0; i < elems.length; i++)
        {
            elems[i] = this.elements[i].mult(normInv);
        }
        return new ElementVector(elems);
    }
    
    public Element norm2()
    {
        Element norm = elements[0].mult( elements[0] );
        for (int i = 1; i < elements.length; i++)
        {
            norm.addTo( elements[i].mult( elements[i] ) );
        }
        return norm;
    }
    
    public Vector add(Vector v)
    {
        Element[] elems = new Element[this.elements.length];
        for (int i = 0; i < elements.length; i++)
        {
            elems[i] = elements[i].add( v.getElement(i) );
        }
        return new ElementVector(elems);
    }
    
    public Vector sub(Vector v)
    {
        Element[] elems = new Element[this.elements.length];
        for (int i = 0; i < elements.length; i++)
        {
            //elems[i] = elements[i].add( v.getElement(i).negate() );
            elems[i] = elements[i].sub( v.getElement(i) );
        }
        return new ElementVector(elems);
    }
    
    public Vector mult(Element e)
    {
        Element[] elems = new Element[this.elements.length];
        for (int i = 0; i < elements.length; i++)
        {
            elems[i] = elements[i].mult( e );
        }
        return new ElementVector(elems);
    }
    
    public Element dot(Vector v )
    {
        ElementVector ev = (ElementVector)v;
        Element dot = this.elements[0].mult( ev.elements[0] );
        for (int i = 1; i < elements.length; i++)
        {
            dot.multAddTo(elements[i], ev.elements[i]);
        }
        return dot;
    }
    
    public Element getElement(int i)
    {
        return this.elements[i];
    }
    
    public void setElement(int i, Element e)
    {
        this.elements[i] = e;
    }
    
    public Element[] getElements()
    {
        return this.elements;
    }
    
    public int getLength()
    {
        return this.elements.length;
    }
    
    public ElementVector mult( IMatrix a )
    {
        List<Vector> vectors = a.getVectors();
        int m = a.getRows();
        Element[] x = this.elements;
        ElementVector bv = (ElementVector)a.getFactory().makeVector(m);
        Element[] b = bv.elements;
        //Element[] b = this.factory.make(vectors.get(0).getLength());
        for (int i = 0; i < x.length; i++)
        {
            //// Slower but more "compatible"
            //Vector v = vectors.get(i);
            ////Element[] vi = vectors.get(i).getElements();
            //Element xWeight = x[i];
            //for (int j = 0; j < v.getLength(); j++)
            //{
            //    b[j].multAddTo(xWeight, v.getElement(j));
            //}
            
            // Faster but assumes ElementVector
            Element[] vi = ((ElementVector)vectors.get(i)).getElements();
            Element xWeight = x[i];
            for (int j = 0; j < vi.length; j++)
            {
                // Interestingly, slower when multiple Element subclasses
                // which suggests dynamic invocation overhead.
                // No intermediate object creation, forces mutable elements.
                b[j].multAddTo(xWeight, vi[j]);
            }
        }
        return bv;
    }
    
    /**
     * Returns true is all the elements are the zero element.
     * @param
     */
    public boolean isZero()
    {
        for (int i = 0; i < elements.length; i++)
        {
            if( elements[i].isZero() == false )
            {
                return false;
            }
        }
        return true;
    }
    
    public ElementVector copy()
    {
        Element[] elems = new Element[this.elements.length];
        for (int i = 0; i < elems.length; i++)
        {
            elems[i] = this.elements[i].copy();
        }
        return new ElementVector(elems);
    }
    
    public ElementVector copy(int start, int length)
    {
        Element[] elems = new Element[length];
        for (int i = 0; i < elems.length; i++)
        {
            elems[i] = this.elements[start+i].copy();
        }
        return new ElementVector(elems);
    }
    
    public void copyElements(int start, Vector v)
    {
        ElementVector ev = (ElementVector)v;
        for (int r = 0; r < ev.elements.length; r++)
        {
            elements[start+r] = ev.elements[r];
        }
    }
    
    public Matrix transpose( Matrix a )
    {
        List<Vector> vectors = a.getVectors();
        List t = new ArrayList<Vector>();
        int n = vectors.size();
        int m = vectors.get(0).getLength();
        
        for (int i = 0; i < m; i++)
        {
            Element[] c = new Element[n];
            for (int j = 0; j < n; j++)
            {
                c[j] = vectors.get(j).getElement(i);
            }
            t.add( new ElementVector(c) );
        }
        return new Matrix(t, a.getFactory());
    }
    
    public Vector extract( int[] B )
    {
        Element[] es = new Element[B.length];
        for (int i = 0; i < es.length; i++)
        {
            es[i] = this.elements[B[i]];
        }
        return new ElementVector(es);
    }
    
    public void extractTo( int[] B, Vector b )
    {
        ElementVector v = (ElementVector)b;
        for (int i = 0; i < B.length; i++)
        {
            v.elements[i] = this.elements[B[i]];
        }
    }
    
    //------------------------------------------------------------------------//
    // Vector specific "row" mutator operations
    //------------------------------------------------------------------------//
    public void swap( int fromRow, int toRow )
    {
        Element temp = this.elements[fromRow];
        this.setElement(fromRow, this.getElement(toRow) );
        this.setElement(toRow, temp );
    }
    
    public void mulAddTo( int fromRow, Element mult, int toRow )
    {
        this.elements[toRow].multAddTo(mult, this.elements[fromRow]);
    }
    
    public void mulTo( int row, Element mult )
    {
        this.getElement(row).multTo(mult);
    }
    
    //------------------------------------------------------------------------//
    // Class instance methods
    //------------------------------------------------------------------------//
    public boolean similar( Object obj, Epsilon e )
    {
        if(obj == this)
        {
            return true;
        }
        
        if( (obj instanceof ElementVector) == false )
        {
            return false;
        }
        
        ElementVector o = (ElementVector)obj;
        
        if( o.elements.length != this.elements.length )
        {
            return false;
        }
        
        if( e != null )
        {
            for (int i = 0; i < this.elements.length; i++)
            {
                // Need epsilon check here
                //if( this.components[i] != o.components[i] )
                if( this.getElement(i).similar(o.getElement(i), e) == false )
                {
                    return false;
                }
            }
        }
        else
        {
            for (int i = 0; i < this.elements.length; i++)
            {
                // Exact check, no epsilon specified
                if( this.elements[i].equals(o.elements[i]) == false )
                {
                    return false;
                }
            }
        }
        
        return true;
    }
    
    public boolean equals( Object obj )
    {
        return this.similar(obj, null);
    }
    
    public String toString()
    {
        StringBuilder buf = new StringBuilder("[");
        for (int i = 0; i < this.elements.length; i++)
        {
            buf.append( this.elements[i].toString() );
            buf.append( "," );
        }
        buf.append("]");
        return buf.toString();
    }
}
